From: Arnaud Rebillout Date: Mon, 6 Apr 2026 11:17:22 +0000 (+0700) Subject: Backport path_startswith_full() X-Git-Tag: archive/raspbian/247.3-7+rpi1+deb11u8^2~6 X-Git-Url: https://dgit.raspbian.org/%22http:/www.example.com/%22mailto:kde%40ewsoftware.de//%22style.css/%22/%22http:/www.example.com/%22mailto:kde%40ewsoftware.de/%22style.css/%22?a=commitdiff_plain;h=d2f89c9b0843a2ade1242dd369016ec2cfe24d06;p=systemd.git Backport path_startswith_full() This is a prerequisite to fix CVE-2026-29111. path_startswith_full() was introduced in systemd v249, in commit 63f11e354a3: "path-util: use path_find_first_component() in path_startswith()". Looking at the commit, we can see that the existing path_startswith() function became a special-case of path_startswith_full(), but there's more to it. path_startswith_full() is also a complete rewrite of the original path_startswith(), and the commit message mentions that the new implementation is stricter. To avoid surprises and potential regressions, this commit opts for a conservative approach: we don't touch the existing path_startswith() function, and we add path_startswith_full() as a entirely new function. Note that it's enough for our purpose: the fix for CVE-2026-29111 makes use of path_startswith_full(). path_startswith_full() was updated after it was introduced in v249: indeed, it was extended to address CVE-2026-29111, and the change was backported to v257. Therefore, this commits takes the function (and associated unit tests) from the v257 branch. Forwarded: not-needed Gbp-Pq: Name CVE-2026-29111-2.patch --- diff --git a/src/basic/path-util.c b/src/basic/path-util.c index d6e66970..41814297 100644 --- a/src/basic/path-util.c +++ b/src/basic/path-util.c @@ -430,6 +430,63 @@ int path_simplify_and_warn( return 0; } +char* path_startswith_full(const char *original_path, const char *prefix, PathStartWithFlags flags) { + assert(original_path); + assert(prefix); + + /* Returns a pointer to the start of the first component after the parts matched by + * the prefix, iff + * - both paths are absolute or both paths are relative, + * and + * - each component in prefix in turn matches a component in path at the same position. + * An empty string will be returned when the prefix and path are equivalent. + * + * Returns NULL otherwise. + */ + + const char *path = original_path; + + if ((path[0] == '/') != (prefix[0] == '/')) + return NULL; + + for (;;) { + const char *p, *q; + int m, n; + + m = path_find_first_component(&path, !FLAGS_SET(flags, PATH_STARTSWITH_REFUSE_DOT_DOT), &p); + if (m < 0) + return NULL; + + n = path_find_first_component(&prefix, !FLAGS_SET(flags, PATH_STARTSWITH_REFUSE_DOT_DOT), &q); + if (n < 0) + return NULL; + + if (n == 0) { + if (!p) + p = path; + + if (FLAGS_SET(flags, PATH_STARTSWITH_RETURN_LEADING_SLASH)) { + + if (p <= original_path) + return NULL; + + p--; + + if (*p != '/') + return NULL; + } + + return (char*) p; + } + + if (m != n) + return NULL; + + if (!strneq(p, q, m)) + return NULL; + } +} + char* path_startswith(const char *path, const char *prefix) { assert(path); assert(prefix); diff --git a/src/basic/path-util.h b/src/basic/path-util.h index af82624e..a1098311 100644 --- a/src/basic/path-util.h +++ b/src/basic/path-util.h @@ -57,6 +57,13 @@ char* path_make_absolute(const char *p, const char *prefix); int safe_getcwd(char **ret); int path_make_absolute_cwd(const char *p, char **ret); int path_make_relative(const char *from_dir, const char *to_path, char **_r); + +typedef enum PathStartWithFlags { + PATH_STARTSWITH_REFUSE_DOT_DOT = 1U << 0, + PATH_STARTSWITH_RETURN_LEADING_SLASH = 1U << 1, +} PathStartWithFlags; + +char* path_startswith_full(const char *path, const char *prefix, PathStartWithFlags flags) _pure_; char* path_startswith(const char *path, const char *prefix) _pure_; int path_compare(const char *a, const char *b) _pure_; bool path_equal(const char *a, const char *b) _pure_; diff --git a/src/test/test-path-util.c b/src/test/test-path-util.c index 699aacef..9f332e39 100644 --- a/src/test/test-path-util.c +++ b/src/test/test-path-util.c @@ -444,6 +444,22 @@ static void test_path_startswith(void) { assert_se(!path_startswith("/foo/bar/barfoo/", "/f/b/b/")); } +static void test_path_startswith_return_leading_slash_one(const char *path, const char *prefix, const char *expected) { + const char *p; + + log_debug("/* %s(%s, %s) */", __func__, path, prefix); + + p = path_startswith_full(path, prefix, PATH_STARTSWITH_RETURN_LEADING_SLASH); + assert_se(streq_ptr(p, expected)); +} + +static void test_path_startswith_return_leading_slash(void) { + test_path_startswith_return_leading_slash_one("/foo/bar", "/", "/foo/bar"); + test_path_startswith_return_leading_slash_one("/foo/bar", "/foo", "/bar"); + test_path_startswith_return_leading_slash_one("/foo/bar", "/foo/bar", NULL); + test_path_startswith_return_leading_slash_one("/foo/bar/", "/foo/bar", "/"); +} + static void test_prefix_root_one(const char *r, const char *p, const char *expected) { _cleanup_free_ char *s = NULL; const char *t; @@ -808,6 +824,7 @@ int main(int argc, char **argv) { test_make_relative(); test_strv_resolve(); test_path_startswith(); + test_path_startswith_return_leading_slash(); test_prefix_root(); test_file_in_same_dir(); test_path_find_first_component();